package at.fhooe.stefanirndorfer.sound;

import java.nio.FloatBuffer;
import java.util.HashMap;

import org.lwjgl.BufferUtils;

import at.fhooe.stefanirndorfer.effects.Effect;
import at.fhooe.stefanirndorfer.effects.Filter;
import at.fhooe.stefanirndorfer.effects.Reverb;
import at.fhooe.stefanirndorfer.logging.Log;

public abstract class SoundPlayer {
	

	private boolean debug;
	
	protected OpenALSource source;
	protected SoundManager sm;
	


	protected HashMap<String, Integer> soundIndices;
	protected String[] soundIdsStrings;
	protected FloatBuffer sourcePos;
	


	protected FloatBuffer sourceVel;
	private String id;
	private float referenceDistance;
	protected int windowWidth;
	protected int windowHeight;
	
	
	


	public SoundPlayer(SoundManager sm, String[] soundIds, double posX, double posY, int windowWidth, int windowHeight){
		id = this.getClass().getSimpleName() + System.nanoTime();
		this.sm = sm;
		source = sm.getOpenALSource();
		this.windowHeight = windowHeight;
		this.windowWidth = windowWidth;
		debug = false;
		
		referenceDistance = OpenALSource.getReferenceDistance();
		initSoundIds(soundIds);
		
		
		//initSourceBuffer();
		initSourcePosition(posX, posY);
		initSourceVelocity();
	}
	
	

/**
 * plays a sound
 * @param id of the sound (buffer)
 * @param s the source the sound should be panned to. if null sound will be played omni
 * @param looping 
 * @param soundfilters if null -- no filters are used. else the filters will be applied in the given order
 */
	public void play(String sId, boolean looping, Filter filter, Effect effect) {
		//int index = soundIndices.get(id);	
			//bindBuffer(sm.getSound(id), index, looping);
			//AL10.alSourcePlay(sourceBuffer.get(index));
			source.play(sm.getOpenALWave(sId), looping, sourcePos, sourceVel, this, filter, effect);
			Log.info(this, "Sound played");
	}

	public void play(String sId) {
		play(sId, false, null, null);
	}
	
	public void playWithReverb(String sId, float decay) {
		Reverb r = new Reverb(decay);
		play(sId, false, null, r);
		
	}
	
	public void playWithReverb(String sId, boolean looping, float decay) {
		Reverb r = new Reverb(decay);
		play(sId, looping, null, r);
	}
	
	public void play(String sId, boolean looping) {
		play(sId, looping, null, null);
	}


	
	
	/**
	 * this method updates the source position on each loop
	 * @param velocity
	 */
	public void updateSourceParams(float[] velocity) {
		// int play;
		for (int i = 0; i < soundIndices.size(); i++) {
			// play = AL10.alGetSourcei(sourceBuffer.get(i),
			// AL10.AL_SOURCE_STATE);
			// if(play == AL10.AL_PLAYING){
			// String name = soundIdsStrings[i];
			// Log.info(this, name +" is playing");

			// sourceVel.put(i*3 + 0, velocity[0]*wNorm);
			// sourceVel.put(i*3 + 1, velocity[1]*hNorm);
			sourceVel.put(i * 3 + 0, velocity[0]);
			sourceVel.put(i * 3 + 1, velocity[1]);

			// sourceVel.put(i*3 + 2, Math.sqrt(1.0f -
			// velocity[0]*velocity[0]));
			sourcePos.put(i * 3 + 0,
					sourcePos.get(i * 3 + 0) + sourceVel.get(i * 3 + 0));
			// Log.info(this, "sourcePos x: " + sourcePos.get(i*3+0));
			sourcePos.put(i * 3 + 1,
					sourcePos.get(i * 3 + 1) + sourceVel.get(i * 3 + 1));

			// calculate arc in order to pan smootly
			double x = sourcePos.get(i*3+0)*2/windowWidth;
			sourcePos.put(i*3+2, (float)Math.sqrt(1.0f - x*x)*referenceDistance);
			// sourcePos.put(i*3+2, sourcePos.get(i*3+2) + 0.1f);

			// Log.info(this, "sourcePos y: " + sourcePos.get(i*3+1));
			// Log.info(this, "sourcePos z: " + sourcePos.get(i*3+2));
			
			/** if debug print euclidean distance source-listener */
			if(debug){
				double a = (double)(sm.getListenerPosition()[0] - sourcePos.get(i*3+0));
				double b = (double)(sm.getListenerPosition()[1] - sourcePos.get(i*3+1));
				double c = (double)(sm.getListenerPosition()[2] - sourcePos.get(i*3+2));
				double dist = Math.sqrt(a*a+b*b+c*c);
				Log.info(this, "dist listener- source: " + dist);
			}
			
			OpenALSource.updateSourceParameters(this, sourceVel, sourcePos);
			//AL10.alSource(sourceBuffer.get(i), AL10.AL_POSITION, sourcePos);
			// }
		}
	}
	
		
	//////////////////////////////////////////////////////
	//// Initialisations
	//////////////////////////////////////////////////////
	
	/**
	 * 
	 * @param posX
	 * @param posY
	 */
	void initSourcePosition(double posX, double posY){
		
		Log.info(this, "Initialize source position x: "+ posX + " and y: " + posY);
		
		int soundIndSize = soundIndices.size();
		sourcePos = (FloatBuffer)BufferUtils.createFloatBuffer(3*soundIndSize);
		for(int i = 0; i < soundIndSize; i++){
			sourcePos.put(i*3+0, (float)posX);
			sourcePos.put(i*3+1, (float)posY);
			sourcePos.put(i*3+2, 0.0f);
			sourcePos.rewind();
		}
	}
		
	private void initSourceVelocity() {
		sourceVel = (FloatBuffer)BufferUtils.createFloatBuffer(3*soundIndices.size()).put(new float[] { 0.0f, 0.0f, 0.0f }).rewind();
	}


	private void initSoundIds(String[] sIds) {
		int l = sIds.length;
		this.soundIndices = new HashMap<String, Integer>();
		this.soundIdsStrings = new String[l];
		for (int i = 0; i < l; i++) {
			this.soundIndices.put(sIds[i], i);
			soundIdsStrings[i] = sIds[i];
		}
	}
	
//	public void initSourceBuffer() {
//		/** Sources are points which emmitt sound. */
//		sourceBuffer = BufferUtils.createIntBuffer(soundIndices.size());
//	}
	
	//////////////////////////////////////////////////////
	//// Getters and Setters
	//////////////////////////////////////////////////////
	
//	public IntBuffer getSourceBuffer() {
//		return sourceBuffer;
//	}
	
	public FloatBuffer getSourcePos() {
		return sourcePos;
	}
	
	
	//////////////////////////////////////////////////////
	//// Abstract Methods
	//////////////////////////////////////////////////////
	
	public abstract void preloadSounds();
	

	public abstract void pause(String id);
	

	public abstract void stop(String id);
	
	public String getId() {
		return id;
	}
	
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((id == null) ? 0 : id.hashCode());
		return result;
	}


	@Override
	public boolean equals(Object obj) {
		if(obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		SoundPlayer other = (SoundPlayer) obj;
		 if (!id.equals(other.id))
			return false;
		return true;
	}
	
	public SoundManager getSm() {
		return sm;
	}

}
